/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pl.abstractvoid.gui;

import java.awt.Component;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.synth.SynthTabbedPaneUI;
import pl.abstractvoid.distributions.AbstractDistribution;
import pl.abstractvoid.languages.LanguageProvider;

/**
 * Component responsible for displaying the distribution input. Data is displayed
 * in the form of nested JTabbedPanes. When a tab is switched, it notifies the 
 * mediator (MainFrame), which in turn notifies all the other components. This 
 * way all the components are displaying information about the currently
 * selected distribution.
 * JTabbedPanes are organized accordingly:
 * DistributionTab - stores tabs tied to distribution class
 *  \->JTabbedPane(s) - are nested within these tabs: they correspond to distribution instance 
 *      \->TablePanel - is nested in each instance tab, it is responsible for
 * displaying and modifying the input data.
 * @author Wojciech Szałapski
 * @author Ivo Zieliński
 */
public class DataInputPanel extends javax.swing.JPanel
        implements DistributionStateObserver {

    private final MainFrame mediator;
    /**
     * Responsible for notifying the mainframe about the tab change, so that 
     * the other components are displaying content correspoinding to appropriate
     * distributions.
     */
    private final ChangeListener changeListener;
    /**
     * Flags that prevent the changeListener from notifying other components
     * about the change of distribution, if the data in other components has
     * not been loaded yet.
     */
    private AtomicBoolean newTabIsBeingCreated;
    private AtomicBoolean tabIsBeingDestroyed;
    /**
     * Stores the instance numbers of currently created distributions. Prevents
     * overlapping name
     */
    private HashMap<String, HashSet<Integer>> tabMap;
    /**
     * Maps JTabbedPanes corresponding to distributions to their names.
     */
    private HashMap<JTabbedPane, String> distributionNames;

    /**
     * 
     * @param mediator to be notified about the tab changes
     */
    public DataInputPanel(final MainFrame mediator) {
        newTabIsBeingCreated = new AtomicBoolean(false);
        tabIsBeingDestroyed = new AtomicBoolean(false);
        tabMap = new HashMap<>();
        distributionNames = new HashMap<>();
        this.mediator = mediator;
        changeListener = new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                if (e.getSource() instanceof JTabbedPane) {
                    if (!newTabIsBeingCreated.get() && !tabIsBeingDestroyed.get()) {
                        mediator.informAboutTabChange(getCurrentTabName());
                    }
                }
            }
        };
        initComponents();
        assignListener();
    }

    /**
     * Updates displayed tab names. Invoked during initial creation and the change of 
     * language.
     */
    public void updateTabNames() {
        if (mediator.getCurrentDistribution() != null) {
            for (int i = 3; i < distributionTab.getComponentCount(); i++) {
                JTabbedPane tab = (JTabbedPane) distributionTab.getComponent(i);
                HashMap<String, AbstractDistribution> distributions = mediator.getDistributions();
                for (Map.Entry pairs : distributions.entrySet()) {
                    if (((String) pairs.getKey()).contains(distributionNames.get(tab))) {
                        distributionTab.setTitleAt(i-3, ((AbstractDistribution) pairs.getValue()).getDistributionName());
                        break;
                    }
                }
            }
        }
    }

    /**
     * Assigns listener to the main tab, so that it can notify the mediator
     * about switching the tab.
     */
    private void assignListener() {
        distributionTab.addChangeListener(changeListener);
    }

    /**
     * Creates new distribution instance tab
     * <p>
     * Populates it with new instance of TablePanel
     */
    public void createDistributionInstanceTab() {
        newTabIsBeingCreated.set(true);
        JTabbedPane selectedTab = null;
        String currentDistributionName = mediator.getCurrentDistribution().getClass().getSimpleName();
        HashSet intSet = tabMap.get(currentDistributionName);
        currentDistributionName = mediator.getCurrentDistribution().getDistributionName();
        for (int i = 0; i < distributionTab.getTabCount(); i++) {
            if (distributionTab.getTitleAt(i).contains(currentDistributionName)) {
                selectedTab = (JTabbedPane) distributionTab.getComponentAt(i);
                break;
            }
        }
        if (selectedTab == null) {
            JOptionPane.showMessageDialog(this, LanguageProvider.getInstance().getString("errorMessage"), LanguageProvider.getInstance().getString("error"), JOptionPane.ERROR_MESSAGE);
            return;
        }
        int distributionNumber;
        for (int i = 1;; i++) {
            if (!intSet.contains(i)) {
                distributionNumber = i;
                break;
            }
        }
        intSet.add(distributionNumber);
        Component newInstance = new TablePanel(mediator);
        selectedTab.addTab(Integer.toString(distributionNumber), newInstance);
        distributionTab.setSelectedComponent(selectedTab);
        selectedTab.setSelectedComponent(newInstance);
        newTabIsBeingCreated.set(false);
    }

    /**
     * Creates new distribution tab
     * <p>
     * Populates it with a single instance tab, with TablePanel as a child
     * component
     */
    public void createDistributionTab() {
        newTabIsBeingCreated.set(true);
        JTabbedPane newDistributionInstance = new JTabbedPane();
        HashSet<Integer> intSet = new HashSet<>();
        intSet.add(1);
        String distributionName = mediator.getCurrentDistribution().getClass().getSimpleName();
        tabMap.put(distributionName, intSet);
        newDistributionInstance.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
        newDistributionInstance.addTab("1", new TablePanel(mediator));
        distributionTab.addTab(mediator.getCurrentDistribution().getDistributionName(), newDistributionInstance);
        distributionTab.setSelectedComponent(newDistributionInstance);
        JTabbedPane newTab = (JTabbedPane) distributionTab.getSelectedComponent();
        distributionNames.put(newTab, distributionName);
        newTab.addChangeListener(changeListener);
        newTabIsBeingCreated.set(false);
    }

    /**
     * Returns the name of currently selected tab. Allows for synchronization
     * between displayed TablePanel and the way dsitributions are mapped in MainFrame.
     * As in MainFrame distributions are mapped to Strings, it requires the current
     * tab name so it can be mapped correctly. When it is notified about the tab change,
     * all MainFrame needs to do is call this method and assign the corresponding
     * distribution to the currentDistribution field.
     * @return 
     */
    public String getCurrentTabName() {
        StringBuilder sb = new StringBuilder();
        JTabbedPane selectedTab = (JTabbedPane) distributionTab.getSelectedComponent();
        int selectedIndex = distributionTab.getSelectedIndex();
        if (selectedIndex == -1) {
            return null;
        }
        sb.append(distributionNames.get(selectedTab));
        selectedIndex = selectedTab.getSelectedIndex();
        if (selectedIndex >= 0) {
            sb.append(selectedTab.getTitleAt(selectedIndex));
        } else {
            distributionTab.remove(selectedTab);
            return getCurrentTabName();
        }
        return sb.toString();
    }

    public boolean isTabIsBeingDestroyed() {
        return tabIsBeingDestroyed.get();
    }

    public void setTabIsBeingDestroyed(boolean tabIsBeingDestroyed) {
        this.tabIsBeingDestroyed.set(tabIsBeingDestroyed);
    }

    @Override
    public void distributionChangedNotify() {
        //Implementation not necessary
    }

    /**
     * Creates new tabs when notified by the mediator (MainFrame). It checks
     * whether it should create new distribution tab, or just another instance.
     */
    @Override
    public void newDistributionNotify() {
        String key = mediator.getCurrentDistribution().getClass().getSimpleName();
        if (distributionNames.containsValue(key)) {
            createDistributionInstanceTab();
        } else {
            createDistributionTab();
        }
        updateUI();
    }

    /**
     * When MainFrame notifies this class about the tab destruction, this method
     * is invoked and all the unecessary swing components are destroyed along
     * with old map entries.
     * @param lastInstanceDeleted 
     */
    @Override
    public void distributionDeletedNotify(boolean lastInstanceDeleted) {
        tabIsBeingDestroyed.set(true);
        JTabbedPane selectedTab = (JTabbedPane) distributionTab.getSelectedComponent();
        if (selectedTab != null) {
            Component tabToBeRemoved = selectedTab.getSelectedComponent();
            String removedTabName = selectedTab.getTitleAt(selectedTab.getSelectedIndex());
            tabMap.get(mediator.getCurrentDistribution().getClass().getSimpleName()).remove(Integer.parseInt(removedTabName));
            selectedTab.remove(tabToBeRemoved);
        }
        if (selectedTab != null && selectedTab.getComponentCount() == 3) {
            distributionNames.remove(selectedTab);
        }
        tabIsBeingDestroyed.set(false);
    }

    /**
     * Reloads parameters to show the change of the current language.
     */
    public void reloadParameters() {
        for (int i = 3; i < distributionTab.getComponentCount(); i++) {
            JTabbedPane selectedTab = (JTabbedPane) distributionTab.getComponent(i);
            for (int j = 0; j < selectedTab.getComponentCount()-3; j++) {
                ((TablePanel) selectedTab.getComponentAt(j)).reloadParameters();
            }
        }
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        distributionTab = new javax.swing.JTabbedPane();

        setLayout(new java.awt.BorderLayout());

        distributionTab.setUI(new SynthTabbedPaneUI() {
            @Override
            protected boolean shouldRotateTabRuns(int tabPlacement) {
                return false;
            }
        });
        distributionTab.setTabPlacement(javax.swing.JTabbedPane.BOTTOM);
        distributionTab.setMinimumSize(new java.awt.Dimension(0, 0));

        distributionTab.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);

        add(distributionTab, java.awt.BorderLayout.CENTER);
    }// </editor-fold>//GEN-END:initComponents
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JTabbedPane distributionTab;
    // End of variables declaration//GEN-END:variables

    /**
     * Invoked during deserialization. Reloads initial components and replaces
     * old maps with new ones.
     */
    @Override
    public void distributionsLoadedNotify() {
        remove(distributionTab);
        initComponents();
        assignListener();
        tabMap = new HashMap<>();
        distributionNames = new HashMap<>();
    }
}
